<!DOCTYPE HTML>
<html>
<head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
    <meta name="google-site-verification" content="KEatQX-J4dYY-6J2KU_aP5X8gAJ8wS0lhylI8umX6WA" />
    <meta name="viewport" content="width=device-width,initial-scale=1,minimal-ui">
    <link rel="shortcut icon" href="../images/favicon.ico">
    <link rel="stylesheet" href="../css/code.css" type="text/css"/>
    <link rel="stylesheet" href="../css/bootstrap.css" type="text/css"/>
    <link rel="stylesheet" href="../css/main.css" type="text/css"/>
    <title>编程小梦|Java并发编程3：设计线程安全的类</title>
</head>
<body>
<nav class="navbar navbar-default navbar-static-top" style="opacity: .9" role="navigation">
    <div class="container-fluid">
        <div class="navbar-header">
            <button type="button" class="navbar-toggle" data-toggle="collapse"
                    data-target="#bs-example-navbar-collapse-1">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
            </button>
            <a class="navbar-brand" href="/">编程小梦</a>
        </div>
        <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
            <ul class="nav navbar-nav navbar-right">
                <li class="active"><a href="/">Blog</a></li>
                
                <li><a href="https://github.com/kangkaisen" target="_blank" rel="nofollow">GitHub</a></li>
                
                
                <li><a href="http://weibo.com/533234148" target="_blank" rel="nofollow">WeiBo</a></li>
                
            </ul>
        </div>
    </div>
</nav>
<div class="row" style="padding-top: 60px">
    <div class="container center-block">
        <div class="col-md-1"></div>
        <div class="col-md-10 col-sm-12">
            <h1> Java并发编程3：设计线程安全的类</h1>
            <hr/>
            <p>作者: 康凯森</p>
            <p>日期: 2016-11-27</p>
            <p>分类: <a href="../tag/Java.html" target="_blank" >Java</a></p>
            <hr/>
            <!-- toc -->
<ul>
<li><a href="#1-设计线程安全的类">1 设计线程安全的类</a></li>
<li><a href="#2-实例封闭">2 实例封闭</a></li>
<li><a href="#3-线程安全性的委托">3 线程安全性的委托</a></li>
<li><a href="#4-在现有的线程安全类中添加功能">4 在现有的线程安全类中添加功能</a></li>
<li><a href="#5-将同步策略文档化">5 将同步策略文档化</a></li>
<li><a href="#6-参考资料">6 参考资料</a></li>
</ul>
<!-- toc stop -->
<h3 id="1-设计线程安全的类">1 设计线程安全的类</h3>
<p>设计线程安全类的基本要素：</p>
<ul>
<li>找出构成对象状态的<strong>所有变量</strong>。</li>
<li>找出约束状态变量的<strong>不变性条件</strong>。</li>
<li>建立对象状态的<strong>并发访问管理策略</strong>。</li>
</ul>
<p>如果对象的所有域都是基本类型的变量，这些变量就构成了对象的全部状态。</p>
<p>如果对象的域中引用了其他对象，那么该对象的状态将包含被引用对象的域。
例如 <code>List</code>的状态就包含列表中所有节点对象的状态。</p>
<p>由于<strong>不变性条件和后验条件</strong>在状态及状态转换上施加了各种约束，因此就需要额外的<strong>同步与封装</strong>。</p>
<p>如果在一个不变性条件中包含多个变量，那么在执行任何访问相关变量的操作时，都必须持有保护这些变量的锁。</p>
<p>当从头开始构建一个类，或者将多个非线程安全的类组合为一个类时，<strong>Java监视器模式</strong>是非常有用的。</p>
<pre><code>/**
 * Created by kangkaisen on 2016/11/27.
 * 使用java监视器模式的线程安全计数器
 */

@ThreadSafe
public class Counter {
    @GuardedBy(&quot;this&quot;)
    private long value = 0;

    public synchronized long getValue() {
        return value;
    }

    public synchronized long increment() {
        if (value == Long.MAX_VALUE) {
            throw new IllegalStateException(&quot;counter overflow&quot;);
        }
        return ++value;
    }
}
</code></pre><h3 id="2-实例封闭">2 实例封闭</h3>
<p><strong>实例封闭是构建线程安全类的一个最简单方式</strong>。</p>
<p>通过将<strong>封闭机制</strong>与合适的<strong>加锁策略</strong>结合起来，可以确保以线程安全的方式来使用非线程安全的对象。</p>
<p>将<strong>数据封装在对象内部</strong>，可以将数据的访问限制在对象的方法上，从而更容易确保线程在访问数据时总能持有正确的锁。</p>
<pre><code>/**
 * Created by kangkaisen on 2016/11/27.
 * 通过封闭机制来确保线程安全
 */
@ThreadSafe
public class PersonSet {
    @GuardedBy(&quot;this&quot;)
    private final Set&lt;Person&gt; myset = new HashSet&lt;&gt;();

    public synchronized void addPerson(Person p) {
        myset.add(p);
    }

    public synchronized boolean containsPerson(Person p) {
        return myset.contains(p);
    }

}
</code></pre><pre><code>/**
 * Created by kangkaisen on 2016/11/27.
 * 通过一个私有锁来保护状态.
 * 私有的锁对象可以将锁封装起来,使客户代码无法得到锁.
 */
@ThreadSafe
public class PrivateLock {
    private final Object myLock = new Object();

    @GuardedBy(&quot;myLock&quot;)
    Person person;

    void someMethod(){
        synchronized (myLock) {
            //访问或修改person的状态
        }
    }
}
</code></pre><h3 id="3-线程安全性的委托">3 线程安全性的委托</h3>
<p>如果一个类是由多个<strong>独立且线程安全</strong>的状态变量组成，并且在所有操作中都<strong>不包含无效的状态转换</strong>，就可以将线程安全性委托给底层的状态变量。（<code>AtomicLong</code>，<code>ConcurrentHashMap</code> 等）</p>
<p>如果某个类有复合操作，例如<code>NumberRange</code> ，那么仅靠委托并不足以实现线程安全性。此时，这个类必须提供<strong>自己的加锁机制</strong>以保证这些<strong>复合操作都是原子操作</strong>，除非<strong>整个复合操作</strong>都可以委托给状态变量。</p>
<pre><code>/**
 * Created by kangkaisen on 2016/11/27.
 * Bad case!
 * NumberRange 类并不足以保护它的不变性条件
 */
public class NumberRange {
    //不变性条件: lower &lt;= upper

    private final AtomicInteger lower = new AtomicInteger(0);
    private final AtomicInteger upper = new AtomicInteger(0);

    public void setLower(int i) {
        // 不安全的 &quot;先检查后执行&quot;
        if (i &gt; upper.get()) {
            throw new IllegalStateException(&quot;can&#39;t set lower, because it &gt; upper&quot;);
        }
        lower.set(i);
    }

    public void setUpper(int i) {
        // 不安全的 &quot;先检查后执行&quot;
        if (i &lt; lower.get()) {
            throw new IllegalStateException(&quot;can&#39;t set upper, because it &lt; lower&quot;);
        }
        upper.set(i);
    }

    public boolean isInRange(int i) {
        return (i &gt;= lower.get() &amp;&amp; i &lt;= upper.get());
    }
}
</code></pre><p>如果一个状态变量是线程安全的，并且<strong>没有任何不变性条件来约束它的值</strong>，在变量的操作上<strong>也不存在任何不允许的状态转换</strong>，那么就可以安全地发布这个变量。</p>
<pre><code>/**
 * Created by kangkaisen on 2016/11/27.
 * 线程安全且可变
 */
@ThreadSafe
public class SafePoint {
    @GuardedBy(&quot;this&quot;)
    private int x, y;

    private SafePoint(int[] a) {
        this(a[0], a[1]);
    }

    public SafePoint(SafePoint p) {
        this(p.get());
    }

    public SafePoint(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public synchronized int[] get() {
        return new int[] { x, y };
    }

    public synchronized void set(int x, int y) {
        this.x = x;
        this.y = y;
    }
}
</code></pre><h3 id="4-在现有的线程安全类中添加功能">4 在现有的线程安全类中添加功能</h3>
<p><strong>重用现有的类可以降低开发工作量，开发风险以及维护成本</strong>。</p>
<p>在现有的线程安全类中添加功能有以下方法：</p>
<ol>
<li>最安全的方法是修改原始的类，但通常无法做到。</li>
<li>扩展一个类，假设这个类在设计时考虑了可扩展性。</li>
<li>客户端加锁机制， 比较脆弱。</li>
<li><strong>组合。</strong>  推荐！！！</li>
</ol>
<p><strong>客户端加锁</strong>是指：对于使用某个对象X的客户端代码，使用X本身保护其自身状态的锁在客户端保护这段代码。 所以必须知道对象X使用的是哪个锁。</p>
<p><strong>通过组合实现在现有的线程安全类中添加功能的代码示例</strong>：</p>
<pre><code>/**
 * Created by kangkaisen on 2016/11/27.
 * 通过组合实现&quot;若没有则添加&quot;
 */

@ThreadSafe
public class ImprovedList&lt;T&gt; implements List&lt;T&gt; {
    private final List&lt;T&gt; list;

    public ImprovedList(List&lt;T&gt; list) {
        this.list = list;
    }

    public synchronized boolean putIfAbsent(T x) {
        boolean contains = list.contains(x);

        if (!contains) {
            list.add(x);
        }

        return contains;
    }

    public synchronized void clear() {
        list.clear();
    }

    //按照类似的方式委托list的其他方法.
}
</code></pre><h3 id="5-将同步策略文档化">5 将同步策略文档化</h3>
<p><strong>在文档中说明客户代码需要了解的线程安全性保证，以及代码维护人员需要了解的同步策略。</strong></p>
<p><strong>如果某个类没有声明是线程安全的，就不要假设它是线程安全的。</strong></p>
<h3 id="6-参考资料">6 参考资料</h3>
<p>本文是《Java并发编程实战》的读书笔记。</p>

            <hr/>
            <div style="padding: 0; margin: 10px auto; width: 90%; text-align: center">
                <button id="rewardButton" , disable="enable" ,
                        onclick="var qr = document.getElementById('QR'); if (qr.style.display === 'none') {qr.style.display='block';} else {qr.style.display='none'}"
                        ,
                        style="cursor: pointer; border: 0; outline: 0; border-radius: 100%; padding: 0; margin: 0; letter-spacing: normal; text-transform: none; text-indent: 0px; text-shadow: none">
                    <span style="display: inline-block; width: 60px; height: 60px; border-radius: 100%; line-height: 58px; color: #fff; font-size:36px; font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, Helvetica, STKaiti, SimSun, serif; background: rgb(236,96,0)">赞</span>
                </button>
                <div id="QR" style="display: none;">
                    <p><img src="../images/weixin.jpeg" width="200" /></p>
                    <p><img src="../images/zhifubao.jpeg" width="200" /></p>
                </div>

            </div>
            <h3>评论</h3>
            <div id="vcomment"></div>
        </div>
        <div class="col-md-1"></div>
    </div>
</div>

<div class="row" style="padding-top: 60px">
    <div class="container center-block">
        <div class="col-md-1"></div>
        <div class="col-md-10 col-sm-12">
            <div class="ds-thread"
                 data-thread-key=5871fac8d2f092c392ca4d62
                 data-title=Java并发编程3：设计线程安全的类
                 data-url=java-concurrent-thread-safe-class>
            </div>
        </div>
        <div class="col-md-1"></div>
    </div>
</div>

<div class="footer">
    <a href="https://www.bcmeng.com/" target="_blank"  rel="nofollow">康凯森</a>
</div>

<script src="../js/code.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
<script src="../js/jquery.min.js"></script>
<script src="../js/bootstrap.js"></script>
<script>
    var _hmt = _hmt || [];
    (function() {
        var hm = document.createElement("script");
        hm.src = "https://hm.baidu.com/hm.js?1d198a377ef466190881d1c021155925";
        var s = document.getElementsByTagName("script")[0];
        s.parentNode.insertBefore(hm, s);
    })();
</script>
<script src="../js/av-min.js"></script>
<script src='../js/Valine.min.js'></script>
<script type="text/javascript">
    window.valine = new Valine({
        el: '#vcomment' ,
        verify: true,
        notify: true,
        appId: 'BlLnB0re5OzQVzrgEplAxkyg-gzGzoHsz',
        appKey: 'wUyxSV0U4Vi7oK1EHK6ipErv',
        placeholder: '欢迎评论'
    });
</script>

</body>
</html>